1 module hip.game2d.text; 2 3 import hip.api.data.font; 4 import hip.api.graphics.text; 5 import hip.util.data_structures; 6 7 8 /** 9 * Formatting the text: 10 * Text should be formatted using the $() syntax. 11 * Currently, no formatting is support, but that syntax is reserved and in the future, it 12 * will be used as for example: $(RGB, 1.0, 1.0, 1.0) or even $(WHITE), so, basic parsing 13 * is being done for accounting how many text does really need to be rendered. 14 */ 15 class HipText 16 { 17 HipTextAlign align_ = HipTextAlign.topLeft; 18 HipFont font; 19 int x, y; 20 bool wordWrap; 21 float scale = 1; 22 23 DirtyFlagsCmp!( 24 shouldUpdateText, x, y, 25 wordWrap, font, 26 align_, 27 ) checkDirty; 28 29 30 float depth = 0; 31 ///Update dynamically based on the font, the text scale and the text content 32 int width, height; 33 34 Size bounds; 35 36 //Line widths, containing width for each line for correctly aplying text align 37 uint[] linesWidths; 38 39 protected string _text; 40 protected string processedText; 41 protected HipColor _color = HipColor.black; 42 43 //Debugging? 44 45 protected bool shouldRenderSpace = false; 46 protected bool shouldRenderLineBreak = false; 47 48 protected HipTextStopConfig[] textConfig; 49 protected HipTextRendererVertexAPI[] vertices; 50 51 //Caching 52 protected size_t _drawableTextCount = 0; 53 protected size_t maxDrawableTextCount = 0; 54 public bool shouldUpdateText = true; 55 56 this(Size bounds = Size.init, bool bWordWrap = false) 57 { 58 import hip.api; 59 checkDirty.start(this); 60 this.font = cast()HipDefaultAssets.getDefaultFont(); 61 linesWidths.length = 1; 62 wordWrap = bWordWrap; 63 this.bounds = bounds; 64 } 65 this(string text, int x, int y, HipFont fnt = null, Size bounds = Size.init, bool bWordWrap = false) 66 { 67 this(bounds, bWordWrap); 68 this.setPosition(x,y); 69 this.text = text; 70 if(fnt) font = fnt; 71 } 72 string text() const {return _text;} 73 size_t drawableTextCount() const {return _drawableTextCount;} 74 75 76 string text(string newText) 77 { 78 if(newText != _text) 79 { 80 import hip.util.string; 81 _drawableTextCount = countVertices(newText); 82 shouldUpdateText = true; 83 if(_drawableTextCount > maxDrawableTextCount) 84 { 85 //As it is a quad, it needs to have vertices * 4 86 vertices.length = _drawableTextCount * 4; 87 maxDrawableTextCount = _drawableTextCount; 88 } 89 _text = newText; 90 } 91 return _text; 92 } 93 94 void setPosition(int x, int y) 95 { 96 this.x = x; 97 this.y = y; 98 } 99 100 HipColor color() => _color; 101 HipColor color(HipColor c) => _color = c; 102 103 void[] getVertices() 104 { 105 checkDirty(); 106 if(shouldUpdateText) 107 { 108 updateText(font); 109 checkDirty.start(this); 110 } 111 112 return cast(void[])vertices[0..drawableTextCount * 4]; 113 } 114 115 protected void updateAlign(int lineNumber, out int displayX, out int displayY, Size bounds) 116 { 117 import hip.api.graphics.text; 118 getPositionFromAlignment(x, y, linesWidths[lineNumber], height, align_, displayX, displayY, bounds); 119 } 120 121 122 public void getSize(out int width, out int height) 123 { 124 if(processedText == null) 125 HipTextStopConfig.parseText(_text, processedText, textConfig); 126 font.calculateTextBounds(processedText, linesWidths, width, height, bounds.width); 127 this.width = width; 128 this.height = height; 129 } 130 public void setAlign(HipTextAlign align_) 131 { 132 this.align_ = align_; 133 } 134 135 package void updateText(IHipFont font) 136 { 137 import hip.api.graphics.text; 138 HipTextStopConfig.parseText(_text, processedText, textConfig); 139 int vI = 0; //vertex buffer index 140 141 vI = putTextVertices(font, vertices[vI..$], processedText, x, y, depth, scale, align_, bounds, wordWrap, shouldRenderSpace); 142 shouldUpdateText = true; 143 } 144 145 void draw() 146 { 147 import hip.api.graphics.g2d.g2d_binding; 148 setTextColor(color); 149 drawTextVertices(getVertices, font); 150 } 151 } 152 153 154 /** 155 * The text stop config defines how this text will behave a 156 */ 157 package struct HipTextStopConfig 158 { 159 import hip.api.graphics.color; 160 int startIndex; 161 HipColorf color; 162 163 //This is just a plan, not supported right now 164 public static enum Tokens 165 { 166 alignh = "alignh", 167 alignv = "alignv", 168 rgb = "rgb", 169 color = "color", 170 bold = "bold", 171 italic = "italic", 172 red = "red", 173 green = "green", 174 blue = "blue", 175 } 176 177 private static HipTextStopConfig parseToken(in string text, size_t indexToParse, out size_t continueIndex) 178 { 179 import hip.util.conv; 180 import hip.util.string; 181 import hip.util.algorithm; 182 int endIndex = text[indexToParse..$].indexOf(")"); //Won't support parenthesis between them. 183 assert(endIndex != -1, "Missing ending parenthesis on string at HipTextStopConfig formatting "); 184 continueIndex = endIndex+indexToParse; 185 186 187 auto range = splitRange(text[indexToParse..endIndex], ","); 188 string token = range.front; 189 range.popFront(); 190 191 switch(token) 192 { 193 case "rgb": 194 { 195 HipColorf c = HipColorf(0, 0, 0, 1.0); 196 range.map((string x) => x.trim.to!float).put(&c.r, &c.g, &c.b); 197 return HipTextStopConfig(cast(int)indexToParse, c); 198 } 199 default: break; 200 } 201 return HipTextStopConfig(cast(int)indexToParse, cast()HipColorf.white); 202 } 203 204 205 static void parseText(string text, out string parsedText, ref HipTextStopConfig[] config) 206 { 207 parsedText = text; 208 // size_t indexConfig = 0; 209 // size_t lastParseIndex = 0; 210 // string parsingText; 211 // for(size_t i = 0; i < text.length; i++) 212 // { 213 // if(i+1 < text.length && text[i] == '$' && text[i+1] == '(') //Found something to parse 214 // { 215 // parsingText~= text[lastParseIndex..i-1]; 216 // HipTextStopConfig cfg = parseToken(text, i+1, i); //Update i 217 // lastParseIndex = i; 218 // if(indexConfig >= config.length) 219 // config.length++; 220 // config[indexConfig++] = cfg; 221 // } 222 // } 223 // //!FIXME: This allocated on each frame. It should both be used a @nogc operation (String) or it should 224 // //!find a way to create a range to be used instead of a string. 225 // if(lastParseIndex == 0) 226 // { 227 // parsedText = text; 228 // return; 229 // } 230 // parsedText = parsingText ~ text[lastParseIndex..$]; 231 } 232 233 } 234 235 236 237 private size_t countVertices(string str) 238 { 239 size_t i = 0; 240 foreach(ch; str) 241 { 242 if(ch != ' ' && ch != '\n') 243 i++; 244 } 245 return i; 246 }